/**
 * Author: David Schneider
 * CSE431 - Translation of Computer Languages
 * Final Project
 */

import JFlex.*;
import coursesolutions.*;
import hw4.*;
import hw3.*;
import java.io.PrintStream;
import java.util.HashMap;


/**
  */

public class CodeGenVisitor extends NodeVisitor {
	
	/**
	 * A flag to of whether or not to print comments in the code.
	 */
	static final boolean comments = false;
	
	
   /**
    * Change this method to return the visitor you want for code generation.
    * As given to you, it runs the course-sponsored solution (CourseProjectCodeGenVisitor);
    * Change the return to "new CodeGenVisitor()" to return an instance of this class instead.
    * @return the visitor for performing code generation
    */


	/* Counts the number of registers in use for the local function */
	int registerCount = 0;
	
	/* Counts the labels */
	int labelCount = 0;
	
   public static ReflectiveVisitor getCodeGenVisitor() { 
      //return new CourseCodeGenVisitor();   // Change this to CodeGenVisitor() to test your code
      return new CodeGenVisitor();
   }

   public static ReflectiveVisitor getTypeSetVisitor() { 
      return new CourseTypeSetVisitor();
   }
   
   private void emit(String s) {
       PrintStream ps = System.out;
       out(ps, s);
   }
   private void emit(NodeDumpable a, String s) {
      if(comments)emit("; " + a.dump());
      emit(s);
   }
   /**
    *	Added plain emit NodeDumpable to allow for emitting the info before calling children 
    */
   private void emit(NodeDumpable a) {
	      if(comments)emit("; " + a.dump());
   }
   private void emitComment(String s) {
      if(comments)emit("; " + s);
   }
   private void skip(int num) {
     for (int i=0; i < num; ++i) emit("");
   }
   
   /**
    * Removes some unwanted characters from class names
    * @param sr
    * @return The new name once cleaned
    */
   private static String cleanClass(StaticReferencing sr){
	   return sr.getClassName().toString().substring(1).replace(';', '/');
   }
   
   /**
    * Removes some unwanted characters from class names
    * @param lr
    * @return The new name once cleaned
    */
   private static String cleanClass(LocalReferencing lr){
	   return lr.getSymInfo().getType().getTypeString().substring(1).replace(';', '/');
   }
   
   /**
    * Removes the first and last character from a String and returns the result
    * @param s
    * @return The new String with the first and last character removed
    */
   private static String trimEnds(String s){
	   return s.substring(1, s.length()-1);
   }
   
  

   /** This outputs a standard prelude, with the class extending Object,
     * a dummy method for main(String[] args) that calls
     * main431
     * Thus, your test file must have a static main431 to kick things
     * off
     */
   public void visit(ClassDeclaring c) {
      emitComment("CSE431 automatically generated code file");
      emitComment("");
      emit(c, ".class public TestClasses/" + c.getName());
      emit(".super java/lang/Object");
      emit(
          "; standard initializer\n\n"+
          ".method public <init>()V\n"+
          "   aload_0\n"+
          "   invokenonvirtual java/lang/Object/<init>()V\n"+
          "   return\n"+
          ".end method\n\n"
        );
      emitComment("dummy main to call our main because we don't handle arrays");
      skip(2);
      emit(
         ".method public static main([Ljava/lang/String;)V\n"+
         "   .limit locals 1\n"+
         "   .limit stack  3\n"+
         "   invokestatic TestClasses/"+c.getName()+"/main431()V\n"+
         "   return\n"+
         ".end method\n\n"
         );
      visitChildren((AbstractNode)c);
   }
   
   /**
    * Inspects an AssignIsh node. Outputs commands to store to a register and 
    * prints information about what it is storing in the register. Also calls 
    * visitChildren on its children.
    * 
    * @param a The node to be parsed as an assignment
    */
   public void visit(AssignIsh a) {
	   emitComment("Found AssignIsh");
	   visitChildren((AbstractNode)a);
	   
	   AbstractNode rl = (((AbstractNode)a).getChild());
	   AbstractNode rr = (((AbstractNode)a).getChild().getSib());
	  
	   emit(rr);
	   emit(rl);
	   
	   if(rr instanceof Referencing){
		   load((Referencing)rr);
	   }
	   
	   if(rl instanceof Referencing){
		   store((Referencing)rl);
	   }

   }
   
   
   /**
    * Inspects a ConstantInt node. Outputs a command to push the value onto the 
    * stack. Also calls VisitChildren on its children.
    * 
    * @param ci The node to be parsed as an integer
    */
   public void visit(ConstantInt ci){
	   emitComment("Found ConstantInt");
	   emit("ldc "+ci.getVal());
	   visitChildren((AbstractNode)ci);

   }
   
   /**
    * Inspects a ConstantBool node. Outputs a command to push the value onto the
    * stack. Also calls VisitChildren on its children.
    * @param cb The node to be parsed as a boolean
    */
   public void visit(ConstantBool cb){
	   emitComment("Found ConstantBool");
	   emit("ldc "+(cb.getVal() ? "1" : "0"));
	   visitChildren((AbstractNode)cb);
   }
   
   /**
    * Inspects a ConstantFloat node. Outputs a command to push the value onto the
    * stack. Also calls VisitChildren on its children.
    * @param cf The node to be parsed as a float
    */
   public void visit(ConstantFloat cf){
	   emitComment("Found ConstantFloat");
	   emit("ldc "+ cf.getVal());
	   visitChildren((AbstractNode)cf);
   }
   
   /**
    * Inspects a ConstantString node. Outputs a command to push the value onto the
    * stack. Also calls VisitChildren on its children.
    * @param cs The node to be parsed as a String
    */
   public void visit(ConstantString cs){
	   emitComment("Found ConstantString");
	   emit("ldc \""+ cs.getVal()+"\"");
	   visitChildren((AbstractNode)cs);
   }
   
   /**
    * General function for loading a variable from any type of reference.
    * This method was prefered over walking the nodes with visitChildren
    * as it can differentiate between a load and a store request.
    * @param r The Referencing node to be loaded
    */
   private void load(Referencing r){
	   if(r instanceof LocalReferencing){
		   load((LocalReferencing)r);
	   }else if(r instanceof StaticReferencing){
		   load((StaticReferencing)r);
	   }else if(r instanceof FieldReferencing){
		   load((FieldReferencing)r);
	   }
   }
   
   /**
    * StaticReferencing function for loading a variable from any type of reference.
    * This method was prefered over walking the nodes with visitChildren
    * as it can differentiate between a load and a store request.
    * @param sr The StaticReferencing node to be loaded
    */
   private void load(StaticReferencing sr){
	   String lib = ( (NodeDumpable) (((AbstractNode)sr).getChild()) ).toString();
	   lib = lib.substring(1);
	   lib = lib.replace(';', '/');
	   emit("getstatic "+lib+sr.getFieldName().toString()+" "+sr.getResultingType().toString());
	  
   }
   
   
   /**
    * LocalReferencing function for loading a variable from any type of reference.
    * This method was prefered over walking the nodes with visitChildren
    * as it can differentiate between a load and a store request.
    * @param lr The LocalReferencing node to be loaded
    */   
   private void load(LocalReferencing lr){
	   int reg = lr.getSymInfo().getRegister();
	   String type = lr.getSymInfo().getType().getTypeString();
	   
	   if(type.equalsIgnoreCase("i") || type.equalsIgnoreCase("z")){
		   emit("iload "+reg);
	   }else if(type.equalsIgnoreCase("f")){
		   emit("fload "+reg);
	   }else if(type.equalsIgnoreCase("d")){
		   emit("dload "+reg);
	   }else if(type.equalsIgnoreCase("l")){
		   emit("lload "+reg);
	   }else{
		   emit("aload "+reg);
	   }
   }

   /**
    * FieldReferencing function for loading a variable from any type of reference.
    * This method was prefered over walking the nodes with visitChildren
    * as it can differentiate between a load and a store request.
    * @param fr The FieldReferencing node to be loaded
    */
   private void load(FieldReferencing fr){
	   
	   emitComment("Load field "+trimEnds(((InvokeReference)fr).getClassType().toString())+fr.getFieldName()+" Type "+fr.getResultingType());
	   emit("getfield "+trimEnds(((InvokeReference)fr).getClassType().toString())+"/"+(fr.getFieldName())+" "+fr.getResultingType());
   }
   
   /**
    * ConstantProducing function for loading a variable from any type of reference.
    * This method is now obsolete as ConstantProducing nodes are walked to load them.
    * @param cp The ConstantProducing node to be loaded
    */
   private void load(ConstantProducing cp){
	   if(cp instanceof ConstantInt){
		   visit((ConstantInt)cp);
	   }else if(cp instanceof ConstantBool){
		   visit((ConstantBool)cp);
	   }else if(cp instanceof ConstantFloat){
		   visit((ConstantFloat)cp);
	   }else if(cp instanceof ConstantString){
		   visit((ConstantString)cp);
	   }
   }
   
   /**
    * Generates a string depicting the type of the ConstantProducing node
    * @param cp 
    * @return  a string depicting the type of the ConstantProducing node
    */
   private String getConstantType(ConstantProducing cp){
	   if(cp instanceof ConstantInt){
		   return "I";
	   }else if(cp instanceof ConstantBool){
		   return "I";
	   }else if(cp instanceof ConstantFloat){
		   return "F";
	   }else if(cp instanceof ConstantString){
		   return "Ljava/lang/String;";
	   }else return "";
   }
   
   /**
    * Generates the code required to store a Referencing node value. Generic function
    * for abstraction. This method was prefered over walking the nodes with visitChildren
    * as it can differentiate between a load and a store request.
    * @param r The Referecing node
    */
   private void store(Referencing r){
	   if(r instanceof LocalReferencing){
		   store((LocalReferencing)r);
	   }else if(r instanceof StaticReferencing){
		   store((StaticReferencing)r);
	   }else if(r instanceof FieldReferencing){
		   store((FieldReferencing)r);
	   }
	   
   }
   
   /**
    * Generates the code required to store a StaticReferencing node value.
    * This method was prefered over walking the nodes with visitChildren
    * as it can differentiate between a load and a store request.
    * @param sr The StaticReferecing node
    */
   private void store(StaticReferencing sr){
	   String lib = ( (NodeDumpable) (((AbstractNode)sr).getChild()) ).toString();
	   lib = lib.substring(1);
	   lib = lib.replace(';', '/');
	   emit("putstatic "+lib+sr.getFieldName().toString()+" "+sr.getResultingType().toString());
	   //putstatic java/lang/System/out Ljava/io/PrintStream;
   }
   
   /**
    * Generates the code required to store a FieldReferencing node value.
    * This method was prefered over walking the nodes with visitChildren
    * as it can differentiate between a load and a store request.
    * @param fr The FieldReferecing node
    */
   private void store(FieldReferencing fr){
	   emitComment("Store field "+trimEnds(((InvokeReference)fr).getClassType().toString())+fr.getFieldName()+" Type "+fr.getResultingType());
	   emit("putfield "+trimEnds(((InvokeReference)fr).getClassType().toString())+"/"+(fr.getFieldName())+" "+fr.getResultingType());
   }
   
   /**
    * Generates the code required to store a LocalReferencing node value.
    * This method was prefered over walking the nodes with visitChildren
    * as it can differentiate between a load and a store request.
    * @param lr The LocalReferecing node
    */
   private void store(LocalReferencing lr){
	   int reg = lr.getSymInfo().getRegister();
	   String type = lr.getSymInfo().getType().getTypeString();
	   
	   if(type.equalsIgnoreCase("i") || type.equalsIgnoreCase("z")){
		   emit("istore "+reg);
	   }else if(type.equalsIgnoreCase("f")){
		   emit("fstore "+reg);
	   }else if(type.equalsIgnoreCase("d")){
		   emit("dstore "+reg);
	   }else if(type.equalsIgnoreCase("l")){
		   emit("lstore "+reg);
	   }else{
		   emit("astore "+reg);
	   }
   }
   
   /**
    * Generates code to invoke a static method. Loads the parameters too.
    * @param sr
    * @param ai
    */
   private void invoke(StaticReferencing sr, ArgIsh ai){
	   emit(sr);
	   emitComment("Invoking StaticReferencing");
	   loadParameters(ai);
	   String result = "invokestatic "+cleanClass(sr)+sr.getFieldName()+"(";
	   result += getParamCallString(ai);
	   result += ")"+sr.getResultingType();
	   emit(result);
   }
   
   /**
    * Generates the code to invoke a field method. Loads the parameters too.
    * @param fr
    * @param ai
    */
   private void invoke(FieldReferencing fr, ArgIsh ai){
	   emit(fr);
	   emitComment("Invoking FieldReferencing");
	  
	   LocalReferencing lr = (LocalReferencing)((AbstractNode)fr).getChild();
	   load(lr);
	   loadParameters(ai);
	   String result = "invokevirtual "+cleanClass(lr)+fr.getFieldName()+"(";
	   result += getParamCallString(ai);
	   result += ")"+fr.getResultingType();
	   emit(result);
	   
   }
   
   /**
    * Generic method for invocation of a function from an InvokeIsh node. This 
    * will generate the function call and load the parameters.
    * @param ii the node to be invoked
    */
   public void visit(InvokeIsh ii){
	   Referencing r = (Referencing)(((AbstractNode)ii).getChild());
	   ArgIsh ai = (ArgIsh)(((AbstractNode)ii).getChild().getSib());
	  
	   if(r instanceof StaticReferencing){
		   invoke((StaticReferencing)r, ai);
	   }else if(r instanceof FieldReferencing){
		   invoke((FieldReferencing)r, ai);
	   }	
   }

   /**
    * Inspects a ComputeIsh. Outputs a command to perform the computation from 
    * values on the stack. Also calls VisitChildren on its children.
    * 
    * @param c The node to be parsed as a computation
    */
   public void visit(ComputeIsh c){
	   visitChildren((AbstractNode)c);
	   emit("i"+c.getOperation());
	   
   }
   
   
   
   /**
    * Inspects a MethodDeclaring. Outputs a command to declare a new method 
    * including declaration and return. Also calls VisitChildren on its children.
    *  
    * @param m The node to be parsed as a method
    */
   public void visit(MethodDeclaring m){
	   String output = "", mods = "";
	   if(m.getMods().isPublic())
		   mods += "public ";
	   if(m.getMods().isStatic())
		   mods += "static ";
	   registerCount = 0;
	   output +=  ".method "+mods+m.getName()+"(";
	   
	   output += getParamDeclareString(((AbstractNode)m).getChild().getChild());
	   output += ")"+m.getType().getTypeString()+"\n"
	   +"          .limit locals 10\n"
	   +"          .limit stack  30";

	   emit(m, output);
	   
	   visitChildren((AbstractNode)m);
	   
	   emit("return\n"
		   +"          .end method\n");
   }
   
   
   /**
    * Generates a string depicting the parameters from an ArgIsh. Used for making
    * code for function calls. Calls getParamCallString(AbstractNode) on a child of
    * ArgIsh to recursively generate the String.
    * @param ai
    * @return The string of parameters
    */
   private String getParamCallString(ArgIsh ai){
	   String result = "";
	   
	   AbstractNode r = (AbstractNode)((AbstractNode)ai).getChild();
	   return getParamCallString(r);
   }
   
   /**
    * Recursively generates a String of parameters useful for makeing function calls
    * @param r The first node to convert into a parameter String
    * @return A String of parameter descriptions
    */
   private String getParamCallString(AbstractNode r){
	   String result = "";
	   
	   if(r == null){
		   return "";
	   }else{
		  
		   if(r instanceof LocalReferencing){
			   result += ((LocalReferencing)r).getSymInfo().getType().getTypeString();
		   }else if(r instanceof StaticReferencing){
			   result += ((StaticReferencing)r).getResultingType().toString();
		   }else if(r instanceof FieldReferencing){
			   result += ((FieldReferencing)r).getResultingType().toString();
		   }else if(r instanceof ConstantProducing){
			   result += getConstantType((ConstantProducing)r);
		   }else if(r instanceof ComputeIsh){
			   result += "I";//Assuming ComputeIsh only supports integers right now
			   				 //Crossing fingers for easy test cases
			                 //Go extreme programming!!!
		   }else if(r instanceof InvokeIsh){
			   result += ((TypeAttrs)((InvokeIsh)r).methodNode().getReturnType()).getTypeString();
		   }
		   r = r.getSib();
		   if(r != null){
			   return result+""+getParamCallString(r);
		   }else{
			   return result;
		   }
	   }
   }
   
   /**
    * Recursively creates the parameter string for a method declaration
    * @param n The first ParamNode for the method declaration
    * @return A string with the param nodes
    */
   private String getParamDeclareString(AbstractNode n){
	   
	   SymDeclaring sd = (SymDeclaring)n;
	   if(sd == null){
		   return "";
	   }else{
		   String result = sd.getSymInfo().getType().getTypeString();
		   
		   n = ((AbstractNode)sd).getSib();
		   if(n != null){
			   return result+""+getParamDeclareString(n);
		   }
		   else return result;
	   }
   }
   
   
   /**
    * Loads parameters onto the stack for calling a function. Calls 
    * loadParams(AbstractNode) on a child of ai.
    * @param ai
    */
   private void loadParameters(ArgIsh ai){
	   loadParams(((AbstractNode)ai).getChild());
   }
   
   /**
    * Loads the parameters onto the stack of the current node and 
    * each of its siblings
    * @param an
    */
   private void loadParams(AbstractNode an){
	   if(an!=null){
		   an.accept(this);
		   loadParams(an.getSib());
	   }
   }
   
   /**
    * Visits the children of a ParamIsh. More or less obsolete.
    * @param p
    */
   public void visit(ParamIsh p){
	   visitChildren((AbstractNode)p);
   }
   
   /**
    * Allocates a register for a local variable and increments the
    * methods register count. No intelligent register allocation currently.
    * @param sd
    */
   public void visit(LocalDeclaring sd){
	   emit(sd);
	   sd.getSymInfo().setRegister(registerCount);
	   ++registerCount;
	   emitComment("Symbol "+sd.getName()+" in reg "+sd.getSymInfo().getRegister()+" type "+sd.getSymInfo().getType());
   }
   
   /**
    * Loads the local reference if the parents is not an AssignIsh. AssignIsh
    * handles its own loading and storing as it performs both tasks
    * @param lr
    */
   public void visit(LocalReferencing lr){
	   if(!(((AbstractNode)lr).getParent() instanceof AssignIsh)){
		   if(lr instanceof LocalReferencing){
			   load((LocalReferencing)lr);
		   }else if(lr instanceof StaticReferencing){
			   load((StaticReferencing)lr);
		   }else if(lr instanceof FieldReferencing){
			   load((FieldReferencing)lr);
		   }
	   }
   }
   
   /**
    * Generates the code to compare values. The code will place 1 onto the
    * stack for true and 0 onto the stack for false.
    * @param ci
    */
   public void visit(CompareIsh ci){
	   visitChildren((AbstractNode)ci);
	   int trueLabel = ++labelCount;
	   int falseLabel = ++labelCount;
	   int endLabel = ++labelCount;
	   emit("if_icmp"+ci.getCompare()+" T"+trueLabel);
	   emit("goto F"+falseLabel);
	   emit("\nT"+trueLabel+":");
	   emit("iconst_1");
	   emit("goto E"+endLabel);
	   emit("\nF"+falseLabel+":");
	   emit("iconst_0");
	   emit("\nE"+endLabel+":");
   }
   
   /**
    * Generates code for the Or operator. Will short the statement
    * if the first parameter is true. The code pushes a 1 onto the stack
    * for true and a 0 onto the stack for false.
    * @param soi
    */
   public void visit(ShortOrIsh soi){

	   int falseLabel = ++labelCount;
	   int endLabel = ++labelCount;
	   AbstractNode l = ((AbstractNode)soi).getChild();
	   AbstractNode r = l.getSib();
	   l.accept(this);
	   emit("ifle F"+falseLabel);

	   emit("iconst_1");
	   emit("goto E"+endLabel);
	   
	   emit("\nF"+falseLabel+":");
	   r.accept(this);
	   
	   emit("\nE"+endLabel+":");
   }
   
   /**
    * Generates code for the And operator. Will short the statement
    * if the first parameter is false. The code pushes a 1 onto the stack
    * for true and a 0 onto the stack for false.
    * @param sai
    */
   public void visit(ShortAndIsh sai){
	   int trueLabel = ++labelCount;
	   int endLabel = ++labelCount;
	   AbstractNode l = ((AbstractNode)sai).getChild();
	   AbstractNode r = l.getSib();
	   l.accept(this);
	   emit("ifgt T"+trueLabel);
	   
	   emit("\nT"+trueLabel+":");
	   r.accept(this);
	   emit("goto E"+endLabel);
	   
	   emit("\nE"+endLabel+":");
   }
   
   /**
    * Generates code for a while loop. Will evaluate and jump from a boolean
    * generate from the predicate. Extra labels and jumps over
    * the most efficient way to avoid inverting the comparison operation.
    * @param wi
    */
   public void visit(WhileIsh wi){
	   int startLabel = (++labelCount);
	   int midLabel = ++labelCount;
	   int endLabel = (++labelCount);
	   emit("\nS"+startLabel+":");
	   wi.getPredicate().accept(this);
	   emit("ifgt"+" M"+midLabel);
	   emit("goto E"+endLabel);
	   emit("\nM"+midLabel+":");
	   wi.getBody().accept(this);
	   emit("goto S"+startLabel);
	   emit("\nE"+endLabel+":");
	   
   }
   
   /**
    * Generates code for an if/else statement. Will evaluate and jump from
    * a boolean generated from the predicate. Extra labels and jumps over
    * the most efficient way to avoid inverting the comparison operation.
    * @param ii
    */
   public void visit(IfIsh ii){
	   ii.getPredicate().accept(this);
	   int trueLabel = ++labelCount;
	   int falseLabel = ++labelCount;
	   int endLabel = ++labelCount;
	   emit("ifgt"+" T"+trueLabel);
	   emit("goto F"+falseLabel);
	   emit("\nT"+trueLabel+":");
	   emitComment("True Part");
	   ii.getTruePart().accept(this);
	   emit("goto E"+endLabel);
	   emit("\nF"+falseLabel+":");
	   emitComment("False Part");
	   ii.getFalsePart().accept(this);
	   emit("\nE"+endLabel+":");
   }

   /**
    * The default visitor that is called when there are no other visits declared 
    * for the node type.
    */
   public void defaultVisit(Object o) {
      AbstractNode n = (AbstractNode) o;
      out("Ignoring " + n.dump());
      visitChildren(n);
   }
   
   

}
